package cn.ycc1.functionlibrary.streams;

/**
 * Using a Collector as a Terminal Operation
 * @author ycc
 * @date 2025/3/8
 */
public class UsingCollector {
    /**
     * Collecting Stream Elements with a Collector
     * You already used a very useful pattern to collect the elements processed by a stream in an List:
     * collect(Collectors.toList()). This collect() method is a terminal method defined in the Stream interface that takes an
     * object of type Collector as an argument. This Collector interface defines an API of its own, that you can use to create
     * any kind of in-memory structure to store the data processed by a stream. Collecting can be made in any instance of
     * Collection or Map, it can be used to create strings of characters, and you can create your own instance of the Collector
     * interface to add your own structures to this list.
     *
     * Most of the collectors you will be using can be created using one of the factory methods of the Collectors factory class.
     * This is what you did when you wrote Collectors.toList(), or Collectors.toSet(). Some collectors created with these methods
     * can be combined, leading to even more collectors. All these points are covered in this tutorial.
     *
     * If you can't find what you need in this factory class, then you can decide to create your own collector by implementing
     * the Collector interface. Implementing this interface is also covered in this tutorial.
     *
     * The Collector API is handled differently in the Stream interface and in the specialized streams of numbers: IntStream,
     * LongStream, and DoubleStream. The Stream interface has two overloads of the collect() method, whereas the streams of
     * numbers have only one. The missing one is precisely the one that takes a collector object as an argument. So you cannot
     * use a collector object with a specialized stream of numbers.
     */

    /**
     * Collecting in a Collection
     * The Collectors factory class gives you three methods to collect the elements of your stream in an instance of the Collection
     * interface.
     *
     * toList() collects them in an List object.
     * toSet() collectors them in a Set object.
     * If you need any other Collection implementation, you can use toCollection(supplier), where the supplier argument will be used
     * to create the Collection object you need. If you need your data to be collected in an instance of LinkedList, this is the
     * method you should be using.
     * Your code should not rely on the exact implementation of List or Set that is currently returned by these methods as it is not
     * part of the specification.
     *
     * You can also get immutable implementations of List and Set using the two methods toUnmodifiableList() and toUnmodifiableSet().
     */

    /**
     * Counting with a Collector
     * The Collectors factory class gives you several methods to create collectors that are doing the same kind of things that a
     * plain terminal method offers you. This is the case for the Collectors.counting() factory method, which does the same thing
     * as calling count() on a stream.
     *
     * This is worth noting, and you may be wondering why such a feature has been implemented twice with two different patterns.
     * This question is answered in the next section about collecting in maps where you will be combining collectors to create
     * more collectors.
     */

    /**
     * Collecting in a String of Characters
     * Another very useful collector provided by the Collectors factory class is the joining() collector. This collector only
     * works on a stream of strings of characters and joins the elements of that stream together in a single string. It has
     * several overloads.
     *
     * The first one takes a separator as an argument.
     * The second one takes a separator, a prefix, and a suffix as arguments.
     */

    /**
     * Partitioning Elements with a Predicate
     * The Collector API offers three patterns to create maps from the elements of a stream. The first one we cover creates map
     * with boolean keys. It is created with the partitionningBy() factory method.
     *
     * All the elements of the stream will be bound to either the true or the false boolean value. The map stores all the
     * elements bound to each value in a list. So, if this collector is applied to a Stream, it will produce a map with the
     * following type: Map<Boolean, List<T>>.
     *
     * Deciding if a given element should be bound to true or false is made by testing this element with a predicate, which is
     * provided as an argument to the collector.
     */

    /**
     * Collecting in a Map with Grouping By
     * The second collector we present is very important because it allows you to create histograms.
     *
     * Grouping the Elements of a Stream in a Map
     * The collector you can use to create histogram is created with the Collectors.groupingBy() method. This method has several
     * overloads.
     *
     * The collector creates a map. A key is computed for each element of the stream by applying an instance of Function to it.
     * This function is provided as an argument of the groupingBy() method. It is called a classifier in the Collector API.
     *
     * There is no restriction on this function apart from the fact that it should not return null.
     *
     * Applying this function may return the same key for more than one element of your stream. The groupingBy() collector
     * supports this, and gather all these elements in a list, bound to that key.
     *
     * So, if you are processing a Stream and use a Function<T, K> as a classifier, the groupingBy() collector creates a
     * Map<K, List<T>>.
     *
     * Post-processing the Values Created with a Grouping By
     * Counting the Lists of Values
     * The groupingBy() method also accepts another argument, which is another collector. This collector is called a downstream
     * collector in the Collector API, but it is just a regular collector. What makes it a downstream collector is the fact
     * that it is passed as an argument to the creation of another collector.
     *
     * This downstream collector is used to collect the values of the map created by the groupingBy() collector.
     *
     * In the previous example, the groupingBy() collector created a map which values are lists of strings. If you give a
     * downstream collector to the groupingBy() method, the API will stream these lists one by one and collect these streams
     * with your downstream collector.
     *
     * Joining the Lists of Values
     * You can also pass the Collectors.joining() collector as a downstream collector, because the values of this map are lists
     * of strings. Remember that this collector can only be used on streams of strings of characters. This creates an instance
     * of Map<Integer, String>: the values take the type created by this collector. You can change the previous example to the
     * following.
     *
     */

    /**
     * Collecting in a Map with To Map
     * The Collector API offers you a second pattern to create maps: the Collectors.toMap() pattern. This pattern works with
     * two functions, both applied to the elements of your stream.
     *
     * The first one is called the key mapper and is used to create the key.
     * The second one is called the value mapper and is used to create the value.
     * This collector is not used in the same cases as the Collectors.groupingBy(). In particular, it does not handle the case
     * where several elements of your stream generate the same key. In that case, by default, an IllegalStateException is raised.
     *
     * This collector is very handy to create caches. Suppose you have a User class with a id property of type Long. You can
     * create a cache of your User objects with the following code.
     */

    /**
     * Extracting Maxes from a Histogram
     * The groupingBy() collector is your best pattern to compute histograms on the data you need to analyze. Let us examine a
     * complete example where you build a histogram and then try to find the maximum value in it based a certain criterion.
     *
     * Extracting a Non-Ambiguous Max
     * The histogram you are going to analyze is the following. It looks like the one we used in a previous example.
     *
     * Extracting an Ambiguous Maximum Value
     * The previous example was a nice example, because there was only one maximum value in your list. Unfortunately, real life
     * cases are often not that nice, and you may have several key-value pairs that match the maximum value.
     *
     */

    /**
     * Using Intermediate Collectors
     * The collectors that we covered so far are counting, joining, and collecting to a list or a map. They are all modeling
     * terminal operations. The Collector API offers other collectors that are conducting intermediate operations: mapping,
     * filtering and flatmapping. You may be wondering what could be the sense of having a terminal method collect() that
     * would model an intermediate operation. In fact, these special collectors cannot be created alone. The factory methods
     * that you can use to create them all need a downstream collector as a second argument.
     *
     * So, the overall collector you can create with these methods is a combination of an intermediate operation and a terminal
     * operation.
     *
     * Mapping with a Collector
     * The first intermediate operation we can examine is the mapping operation. A mapping collector is created with the
     * Collectors.mapping() factory method. It takes a regular mapping function as a first argument and a mandatory downstream
     * collector as a second argument.
     *
     * Filtering and Flatmapping with a Collector
     * The filtering collector follows the same pattern as the mapping collector. It is created with the Collectors.filtering()
     * factory method that takes a regular predicate to filter the data and a mandatory downstream collector.
     *
     * The same goes for the flatmapping collector, created by the Collectors.flatMapping() factory method, that takes a
     * flatmapping function (a function that returns a stream), and a mandatory downstream collector.
     *
     * Using Terminal Collectors
     * There Collector API also offers several terminal operations that correspond to terminal operations available on the
     * Stream API.
     *
     * maxBy(), and minBy(). These two methods both take a comparator as an argument and return an optional object that is empty
     * if the stream processed is itself empty.
     * summingInt(), summingLong(), and summingDouble(). These three methods take a mapping function as an argument to map the
     * element of your stream to int, long and double respectively, before summing them.
     * averagingInt(), averagingLong(), and averagingDouble(). These three methods also takes a mapping function as an argument,
     * to map the element of your stream to int, long and double, respectively, before computing the average. These collectors
     * do not work the same as the corresponding average() methods defined in IntStream, LongStream, and DoubleStream. They all
     * return a Double instance and return 0 for empty streams. The average() methods return an optional object that is empty
     * for empty streams.
     */


}
